/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007-2008 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.config.autoconfiguration.process;

import java.io.File;
import java.io.IOException;
import java.util.Properties;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import com.genia.toolbox.config.autoconfiguration.bean.DatabaseConfigurationBean;

/**
 * enum with all provider for a managed database connection.
 */
public enum DatabasePoolConfigurator
{

  /**
   * the default provider.
   */
  DEFAULT
  {
    /**
     * append the spring declaration to add for a connection to the database
     * described by the {@link DatabaseConfigurationBean}.
     * 
     * @param databaseConfigurationBean
     *          the description for the database connection
     * @param appendable
     *          the {@link Appendable} to append the configuration to
     * @throws IOException
     *           when an error occured
     */
    @Override
    public void appendSpringDeclaration(final DatabaseConfigurationBean databaseConfigurationBean, final Appendable appendable)
        throws IOException
    {
      appendable.append("<bean class=\"org.jencks.tranql.DataSourceMCF\">");
      appendable.append("<property name=\"driverName\" value=\"").append(databaseConfigurationBean.getDriver()).append("\" />");
      appendable.append("<property name=\"url\" value=\"").append(databaseConfigurationBean.getUrl()).append("\" />");
      appendable.append("<property name=\"user\" value=\"").append(databaseConfigurationBean.getLogin()).append("\" />");
      appendable.append("<property name=\"password\" value=\"").append(databaseConfigurationBean.getPassword()).append("\" />");
      appendable.append("</bean>");
    }



    /**
     * returns wether this {@link DatabasePoolConfigurator} can handle the
     * databse described by the {@link DatabaseConfigurationBean}.
     * 
     * @param databaseConfigurationBean
     *          the description for the database connection
     * @return wether this {@link DatabasePoolConfigurator} can handle the
     *         databse described by the {@link DatabaseConfigurationBean}
     */
    @Override
    public boolean canHandle(final DatabaseConfigurationBean databaseConfigurationBean)
    {
      return true;
    }

  },
  /**
   * the derby provider.
   */
  DERBY
  {
    /**
     * append the spring declaration to add for a connection to the database
     * described by the {@link DatabaseConfigurationBean}.
     * 
     * @param databaseConfigurationBean
     *          the description for the database connection
     * @param appendable
     *          the {@link Appendable} to append the configuration to
     * @throws IOException
     *           when an error occured
     */
    @Override
    public void appendSpringDeclaration(final DatabaseConfigurationBean databaseConfigurationBean, final Appendable appendable)
        throws IOException
    {
      try {
        File configurationDirectory = ProcessConfiguration.getConfigurationBean().getConfigurationDirectory();

        // Where to put the derby log file.
        System.setProperty("derby.stream.error.file", new File(configurationDirectory, "derby.log").getAbsolutePath());

        String[] properties = databaseConfigurationBean.getUrl().split(":");
        String databaseName = properties[properties.length - 1].split(";")[0].replaceAll("\\$\\{contentDirectory\\}", configurationDirectory.getCanonicalPath());
        appendable.append("<bean class=\"org.tranql.connector.derby.EmbeddedXAMCF\">");
        appendable.append("<property name=\"userName\" value=\"").append(databaseConfigurationBean.getLogin()).append("\" />");
        appendable.append("<property name=\"password\" value=\"").append(databaseConfigurationBean.getPassword()).append("\" />");
        appendable.append("<property name=\"databaseName\" value=\"").append(databaseName).append("\" />");
        appendable.append("<property name=\"createDatabase\" value=\"").append(String.valueOf(databaseConfigurationBean.getSchemaUpdate())).append("\" />");
        appendable.append("</bean>");
      }
      catch (Exception e) {
        LOGGER.error(e.getMessage(), e);
        throw new IOException(e.getMessage());
      }
    }



    /**
     * returns wether this {@link DatabasePoolConfigurator} can handle the
     * databse described by the {@link DatabaseConfigurationBean}.
     * 
     * @param databaseConfigurationBean
     *          the description for the database connection
     * @return wether this {@link DatabasePoolConfigurator} can handle the
     *         databse described by the {@link DatabaseConfigurationBean}
     */
    @Override
    public boolean canHandle(final DatabaseConfigurationBean databaseConfigurationBean)
    {
      return databaseConfigurationBean.getUrl().startsWith("jdbc:derby:");
    }
  },
  /**
   * the postgres provider.
   */
  POSTGRES
  {
    /**
     * append the spring declaration to add for a connection to the database
     * described by the {@link DatabaseConfigurationBean}.
     * 
     * @param databaseConfigurationBean
     *          the description for the database connection
     * @param appendable
     *          the {@link Appendable} to append the configuration to
     * @throws IOException
     *           when an error occured
     */
    @Override
    public void appendSpringDeclaration(final DatabaseConfigurationBean databaseConfigurationBean, final Appendable appendable)
        throws IOException
    {
      Properties properties = PostgresUrlParser.decodeUrl(databaseConfigurationBean.getUrl());
      if (properties == null) {
        fallback("postgres", databaseConfigurationBean, appendable);
        return;
      }
      appendable.append("<bean class=\"org.tranql.connector.postgresql.PGXAMCF\">");
      appendable.append("<property name=\"userName\" value=\"").append(databaseConfigurationBean.getLogin()).append("\" />");
      appendable.append("<property name=\"password\" value=\"").append(databaseConfigurationBean.getPassword()).append("\" />");
      if (properties.getProperty(PostgresUrlParser.PGHOST) != null) {
        appendable.append("<property name=\"serverName\" value=\"").append(properties.getProperty(PostgresUrlParser.PGHOST)).append("\" />");
      }
      if (properties.getProperty(PostgresUrlParser.PGPORT) != null) {
        appendable.append("<property name=\"portNumber\" value=\"").append(properties.getProperty(PostgresUrlParser.PGPORT)).append("\" />");
      }
      if (properties.getProperty(PostgresUrlParser.PGDBNAME) != null) {
        appendable.append("<property name=\"databaseName\" value=\"").append(properties.getProperty(PostgresUrlParser.PGDBNAME)).append("\" />");
      }
      appendable.append("</bean>");
    }



    /**
     * returns wether this {@link DatabasePoolConfigurator} can handle the
     * databse described by the {@link DatabaseConfigurationBean}.
     * 
     * @param databaseConfigurationBean
     *          the description for the database connection
     * @return wether this {@link DatabasePoolConfigurator} can handle the
     *         databse described by the {@link DatabaseConfigurationBean}
     */
    @Override
    public boolean canHandle(final DatabaseConfigurationBean databaseConfigurationBean)
    {
      return PostgresUrlParser.decodeUrl(databaseConfigurationBean.getUrl()) != null;
    }

  },

  /**
   * the oracle driver.
   */
  ORACLE
  {
    /**
     * append the spring declaration to add for a connection to the database
     * described by the {@link DatabaseConfigurationBean}.
     * 
     * @param databaseConfigurationBean
     *          the description for the database connection
     * @param appendable
     *          the {@link Appendable} to append the configuration to
     * @throws IOException
     *           when an error occured
     */
    @Override
    public void appendSpringDeclaration(final DatabaseConfigurationBean databaseConfigurationBean, final Appendable appendable)
        throws IOException
    {
      String jdbcUrl = databaseConfigurationBean.getUrl();
      String[] urlParts = jdbcUrl.split("@");
      if (urlParts.length != 2) {
        fallback("oracle", databaseConfigurationBean, appendable);
        return;
      }
      String[] typeElements = urlParts[0].split(":");
      if (!urlParts[0].matches("[^:]*:[^:]*:[^:]*:[^:]*")) {
        fallback("oracle", databaseConfigurationBean, appendable);
        return;
      }
      String driverType = typeElements[2];
      if (!"thin".equalsIgnoreCase(driverType)) {
        fallback("oracle", databaseConfigurationBean, appendable);
        return;
      }
      String hostAndDb = urlParts[1];
      if (hostAndDb.startsWith("//")) {
        hostAndDb = urlParts[1].substring(2);
      }
      String[] hostAndDbElements = hostAndDb.split(":");
      if (hostAndDbElements.length != 3) {
        fallback("oracle", databaseConfigurationBean, appendable);
        return;
      }
      String serverName = hostAndDbElements[0];
      String portNumber = hostAndDbElements[1];
      String serviceName = hostAndDbElements[2];
      appendable.append("<bean class=\"org.tranql.connector.oracle.XAMCF\">");
      appendable.append("<property name=\"userName\" value=\"").append(databaseConfigurationBean.getLogin()).append("\" />");
      appendable.append("<property name=\"password\" value=\"").append(databaseConfigurationBean.getPassword()).append("\" />");
      appendable.append("<property name=\"serverName\" value=\"").append(serverName).append("\" />");
      appendable.append("<property name=\"driverType\" value=\"").append(driverType).append("\" />");
      appendable.append("<property name=\"portNumber\" value=\"").append(portNumber).append("\" />");
      appendable.append("<property name=\"serviceName\" value=\"").append(serviceName).append("\" />");
      appendable.append("</bean>");
    }



    /**
     * returns wether this {@link DatabasePoolConfigurator} can handle the
     * databse described by the {@link DatabaseConfigurationBean}.
     * 
     * @param databaseConfigurationBean
     *          the description for the database connection
     * @return wether this {@link DatabasePoolConfigurator} can handle the
     *         databse described by the {@link DatabaseConfigurationBean}
     */
    @Override
    public boolean canHandle(final DatabaseConfigurationBean databaseConfigurationBean)
    {
      return databaseConfigurationBean.getUrl().startsWith("jdbc:oracle:");
    }

  },
  /**
   * the oracle driver.
   */
  MYSQL
  {
    /**
     * append the spring declaration to add for a connection to the database
     * described by the {@link DatabaseConfigurationBean}.
     * 
     * @param databaseConfigurationBean
     *          the description for the database connection
     * @param appendable
     *          the {@link Appendable} to append the configuration to
     * @throws IOException
     *           when an error occured
     */
    @Override
    public void appendSpringDeclaration(final DatabaseConfigurationBean databaseConfigurationBean, final Appendable appendable)
        throws IOException
    {
      String serverName = "127.0.0.1";
      String portNumber = "3306";
      String jdbcUrl = databaseConfigurationBean.getUrl();
      if (!jdbcUrl.startsWith("jdbc:mysql://")) {
        fallback("mysql", databaseConfigurationBean, appendable);
        return;
      }
      jdbcUrl = jdbcUrl.substring("jdbc:mysql://".length());
      String[] urlElements = jdbcUrl.split("/");
      if (urlElements.length != 2) {
        fallback("mysql", databaseConfigurationBean, appendable);
        return;
      }
      String[] hostAndPort = urlElements[0].split(":");
      if (hostAndPort.length > 2) {
        fallback("mysql", databaseConfigurationBean, appendable);
        return;
      }
      if (StringUtils.hasText(hostAndPort[0])) {
        serverName = hostAndPort[0];
      }
      if (hostAndPort.length == 2) {
        portNumber = hostAndPort[1];
      }
      String databaseName = urlElements[1];
      appendable.append("<bean class=\"org.tranql.connector.mysql.XAMCF\">");
      appendable.append("<property name=\"userName\" value=\"").append(databaseConfigurationBean.getLogin()).append("\" />");
      appendable.append("<property name=\"password\" value=\"").append(databaseConfigurationBean.getPassword()).append("\" />");
      appendable.append("<property name=\"serverName\" value=\"").append(serverName).append("\" />");
      appendable.append("<property name=\"portNumber\" value=\"").append(portNumber).append("\" />");
      appendable.append("<property name=\"databaseName\" value=\"").append(databaseName).append("\" />");
      appendable.append("</bean>");
    }



    /**
     * returns wether this {@link DatabasePoolConfigurator} can handle the
     * databse described by the {@link DatabaseConfigurationBean}.
     * 
     * @param databaseConfigurationBean
     *          the description for the database connection
     * @return wether this {@link DatabasePoolConfigurator} can handle the
     *         databse described by the {@link DatabaseConfigurationBean}
     */
    @Override
    public boolean canHandle(final DatabaseConfigurationBean databaseConfigurationBean)
    {
      return databaseConfigurationBean.getUrl().startsWith("jdbc:mysql:");
    }

  };

  /**
   * the <code>Log</code> variabe.
   */
  private static final Logger LOGGER = LoggerFactory.getLogger(DatabasePoolConfigurator.class);



  /**
   * append the spring declaration to add for a connection to the database
   * described by the {@link DatabaseConfigurationBean}.
   * 
   * @param databaseConfigurationBean
   *          the description for the database connection
   * @param appendable
   *          the {@link Appendable} to append the configuration to
   * @throws IOException
   *           when an error occured
   */
  public abstract void appendSpringDeclaration(final DatabaseConfigurationBean databaseConfigurationBean, final Appendable appendable)
      throws IOException;



  /**
   * returns wether this {@link DatabasePoolConfigurator} can handle the databse
   * described by the {@link DatabaseConfigurationBean}.
   * 
   * @param databaseConfigurationBean
   *          the description for the database connection
   * @return wether this {@link DatabasePoolConfigurator} can handle the databse
   *         described by the {@link DatabaseConfigurationBean}
   */
  public abstract boolean canHandle(final DatabaseConfigurationBean databaseConfigurationBean);



  /**
   * fallback method if a jdbc url is not parsed correctly.
   * 
   * @param dbType
   *          the type of the database
   * @param databaseConfigurationBean
   *          the description for the database connection
   * @param appendable
   *          the {@link Appendable} to append the configuration to
   * @throws IOException
   *           when an error occured
   */
  protected void fallback(String dbType, final DatabaseConfigurationBean databaseConfigurationBean, final Appendable appendable)
      throws IOException
  {
    LOGGER.warn("Unable to parse " + dbType + " URL (" + databaseConfigurationBean.getUrl() + "), using default datasource implementation.");
    DEFAULT.appendSpringDeclaration(databaseConfigurationBean, appendable);
    return;
  }
}
